﻿#include "Wordlist.h"

using namespace std;

const int FUNC_NULL = 0;
const int FUNC_N = 1;
const int FUNC_W = 2;
const int FUNC_M = 3;
const int FUNC_C = 4;

char usage[] = "\n"
               "Usage: \n"
               "  Wordlist <function> <filename> [options]\n"
               "\n"
               "Functions:\n"
               "  -n             Get the total number of word chains.\n"
               "  -w             Get the word chain with the most words.\n"
               "  -m             Get the word chain with the most words, where the\n"
               "                 first letter of each word cannot be repeated.\n"
               "  -c             Get the word chain with the most letters.\n"
               "\n"
               "Options:\n"
               "  -h <head>      Specify the first letter of the chain.\n"
               "  -t <tail>      Specify the last letter of the chain.\n"
               "  -r             Allow implicit word circles.\n"
               "\n";

void print_usage() {
    printf("%s", usage);
    exit(-1);
}

void set_function(int &function, int usr_func, char *argv) {
    if (!function) {
        function = usr_func;
    } else {
        fprintf(stderr, "\n%s: Duplicated function specified.\n", argv);
        print_usage();
    }
}


FILE *check_file(const string& filename) {
    struct stat s{};
    const char *path = filename.c_str();
    if (filename.size() < 4 || filename.substr(filename.size() - 4, 4) != ".txt") {
        fprintf(stderr, "Error: Illegal file \"%s\".\n", path);
        exit(-1);
    }
    FILE *ret = nullptr;
    if (stat(path, &s) == 0) {
        if (s.st_mode & S_IFREG) {
            if (fopen_s(&ret, path, "r") != 0) {
                fprintf(stderr, "Error: Cannot open file \"%s\".\n", path);
                exit(-1);
            }
        } else {
            fprintf(stderr, "Error: Illegal file \"%s\".\n", path);
            exit(-1);
        }
    } else {
        fprintf(stderr, "Error: File \"%s\" not exists.\n", path);
        exit(-1);
    }
    return ret;
}

int get_words_from_filename(const string& filename, char *word_buf, char **words) {
    FILE *file = check_file(filename);

    const int BEGIN = 0;
    const int READING = 1;

    unordered_set<string> filter;
    vector<string> ret;

    char c;
    char buf[1024];
    int state = BEGIN;
    int top = 0;
    while ((c = (char) fgetc(file)) != EOF) {
        if (state == BEGIN) {
            if ('a' <= c && c <= 'z'){
                *buf = (char) c;
                top = 1;
                state = READING;
            } else if ('A' <= c && c <= 'Z') {
                *buf = (char) (c + 0x20);
                top = 1;
                state = READING;
            }
        } else { // state == READING
            if ('a' <= c && c <= 'z'){
                buf[top] = c;
                top++;
            } else if ('A' <= c && c <= 'Z') {
                buf[top] = (char) (c + 0x20);
                top++;
            } else {
                state = BEGIN;
                buf[top] = 0;
                if (top > 1) {
                    filter.insert(buf);
                }
            }
        }
    }
    if (state == READING && top > 1) {
        buf[top] = 0;
        filter.insert(buf);
    }
    top = 0;
    int num = 0;
    for (const auto& w : filter) {
        const char *wd = w.c_str();
        words[num++] = word_buf + top;
        int i = 0;
        while (wd[i]) {
            word_buf[top++] = wd[i++];
        }
        word_buf[top++] = 0;
    }
    return (int) filter.size();
}

void get_options(char &head, char &tail, bool &allow_circ, int &function, string &input_filename, OptParser &parser, char *argv) {
    int opt;
    while ((opt = parser.next_opt()) != NULL) {
        if (opt == -1) {
            print_usage();
        } else if (opt == 1) {
            if (input_filename.empty()) {
                input_filename = parser.param;
            } else {
                fprintf(stderr, "\n%s: Extra parameter \"%s\".\n", argv, parser.param.c_str());
                print_usage();
            }
        } else {
            switch (opt) {
                case 'n':
                    set_function(function, FUNC_N, argv);
                    input_filename = parser.param;
                    break;
                case 'w':
                    set_function(function, FUNC_W, argv);
                    input_filename = parser.param;
                    break;
                case 'm':
                    set_function(function, FUNC_M, argv);
                    input_filename = parser.param;
                    break;
                case 'c':
                    set_function(function, FUNC_C, argv);
                    input_filename = parser.param;
                    break;
                case 'h':
                    if (!head) {
                        if (parser.param.size() != 1) {
                            fprintf(stderr, "\n%s: Specified head must be a single letter.\n", argv);
                            print_usage();
                        } else if ('a' <= parser.param[0] && parser.param[0] <= 'z') {
                            head = parser.param[0];
                        } else if ('A' <= parser.param[0] && parser.param[0] <= 'Z') {
                            head = (char) (parser.param[0] - (int) 0x20);
                        } else {
                            fprintf(stderr, "\n%s: Specified head must be a letter.\n", argv);
                            print_usage();
                        }
                    } else {
                        fprintf(stderr, "\n%s: Duplicated head specified.\n", argv);
                        print_usage();
                    }
                    break;
                case 't':
                    if (!tail) {
                        if (parser.param.size() != 1) {
                            fprintf(stderr, "\n%s: Specified tail must be a single letter.\n", argv);
                            print_usage();
                        } else if ('a' <= parser.param[0] && parser.param[0] <= 'z') {
                            tail = parser.param[0];
                        } else if ('A' <= parser.param[0] && parser.param[0] <= 'Z') {
                            tail = (char) (parser.param[0] - (int) 0x20);
                        } else {
                            fprintf(stderr, "\n%s: Specified tail must be a letter.\n", argv);
                            print_usage();
                        }
                    } else {
                        fprintf(stderr, "\n%s: Duplicated tail specified.\n", argv);
                        print_usage();
                    }
                    break;
                case 'r':
                    if (!allow_circ) {
                        allow_circ = true;
                    } else {
                        fprintf(stderr, "\n%s: Duplicated -r specified.\n", argv);
                        print_usage();
                    }
                    break;
                default:
                    print_usage();
                    break;
            }
        }
    }

    switch (function) {
        case FUNC_N:
        case FUNC_M:
            if (head || tail || allow_circ) {
                fprintf(stderr, "\n%s: -n or -m option cannot be with -h -t or -r options.\n", argv);
                print_usage();
            }
            break;
        case FUNC_NULL:
            fprintf(stderr, "\n%s: No function specified.\n", argv);
            print_usage();
        default:
            break;
    }

    if (input_filename.empty()) {
        fprintf(stderr, "\n%s: No input file specified.\n", argv);
        print_usage();
    }
}

void print_to_file(int n_return, char *result[]) {
    FILE *output_file;
    fopen_s(&output_file, "solution.txt", "w");
    if (n_return <= 20000) {
        for (int i = 0; i < n_return; i++) {
            fprintf(output_file, "%s\n", result[i]);
        }
    } else {
        fprintf(stderr, "Results are too long. %d results\n", n_return);
    }
    fclose(output_file);
}

int main(int argc, char *argv[]) {
    if (argc == 1) {
        print_usage();
    }

    char head = 0, tail = 0;
    bool allow_circ = false;
    int function = FUNC_NULL;
    string input_filename;

    char pattern[] = "nwmch:t:r?";
    OptParser opt_parser(argc, argv, pattern, false);
    get_options(head, tail, allow_circ, function, input_filename, opt_parser, *argv);

    char *word_buf = (char *) malloc(0x4000000);
    char **words = (char**) malloc (sizeof (char*) * MAX_WORD_NUM);
    int n_words;

    n_words = get_words_from_filename(input_filename, word_buf, words);

    char *result_buf = (char *) malloc(0x10000000);
    char **result = (char**) malloc (sizeof (char*) * MAX_CHAIN_NUM);
    *result = result_buf;
    int n_return;

    HMODULE core = LoadLibrary("core.dll");

    try {
        switch (function) {
            case FUNC_N:
                n_return = gen_chains_all(words, n_words, result);
                printf("%d\n", n_return);
                for (int i = 0; i < n_return; i++){
                    printf("%s\n", result[i]);
                }
                break;
            case FUNC_W:
                n_return = gen_chain_word(words, n_words, result, head, tail, allow_circ);
                print_to_file(n_return, result);
                break;
            case FUNC_M:
                n_return = gen_chain_word_unique(words, n_words, result);
                print_to_file(n_return, result);
                break;
            case FUNC_C:
                n_return = gen_chain_char(words, n_words, result, head, tail, allow_circ);
                print_to_file(n_return, result);
                break;
            default:
                fprintf(stderr, "\n%s: No function specified.\n", *argv);
                print_usage();
                break;
        }
    } catch (exception &e) {
        fprintf(stderr, "%s\n", e.what());
    }


    FreeLibrary(core);

	return 0;
}
